package org.ntu.rtsearch.search;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;

import org.ntu.rtsearch.index.MutiIndexWriter;
import org.ntu.rtsearch.index.model.Document;
import org.ntu.rtsearch.index.model.InvertedIndex;
import org.ntu.rtsearch.index.model.Posting;
import org.ntu.rtsearch.query.Query;
import org.ntu.rtsearch.search.model.ScoreDoc;
import org.ntu.rtsearch.search.model.Scorer;
import org.ntu.rtsearch.search.model.TopDocs;

public class MutiIndexSearcher {
	
	private List<InvertedIndex> indexList = new ArrayList<InvertedIndex>();
	
	private int docAmount = 0;
	
	public MutiIndexSearcher(MutiIndexWriter writer) {
		for (InvertedIndex index : writer.getIndexMap().values()) {
			docAmount += index.getDocCount();
			indexList.add(index);
		}
		indexList.add(writer.getIndexWriter().getIndex());	
		
		docAmount += writer.getIndexWriter().getIndex().getDocCount();
	}
	
	public TopDocs search(Query q, int n) {
		List<LinkedList<Posting>> postingLists = null;
		List<Queue<ScoreDoc>> qList = new ArrayList<Queue<ScoreDoc>>();
		
		// scan all the index, and get top n docs from each index
		for (InvertedIndex index : indexList) {
			postingLists = new ArrayList<LinkedList<Posting>>();
			for (String term : q.getTerms()) {
				List<Posting> postingList = index.getPostingList(term);
				if(postingList != null)
					postingLists.add(new LinkedList<Posting>(postingList));
			}
			qList.add(docAtATime(postingLists, n));
		}
		
		// get final top n highest score docs from all the indexes' top n docs
		PriorityQueue<ScoreDoc> pq = new PriorityQueue<ScoreDoc>(n, ScoreDoc.getComp());
		for (Queue<ScoreDoc> queue : qList) {
			for (ScoreDoc scoreDoc : queue) {
				if(pq.size() == n) {
					if(scoreDoc.score > pq.peek().score) {
						pq.poll();
						pq.add(scoreDoc);
					}
				} else {
					pq.add(scoreDoc);
				}
			}
		}
		return new TopDocs(pq, n);
	}
	
	/**
	 * search by document at a time, scan the posting list twice
	 * @param postingLists
	 * @param n
	 * @return top n largest score document
	 */
	public Queue<ScoreDoc> docAtATime(List<LinkedList<Posting>> postingLists, int n) {
		long curTime = System.currentTimeMillis();
		PriorityQueue<ScoreDoc> queue = new PriorityQueue<ScoreDoc>(n, ScoreDoc.getComp()); 

		boolean isAllEmpty = false;
		Posting posting = null;
		while(!isAllEmpty) {
			Scorer scorer = new Scorer(curTime);
			isAllEmpty = true;
			int minDocId = getMinDocId(postingLists);
			for(LinkedList<Posting> postingList : postingLists) {
				if(!postingList.isEmpty()) {
					posting = postingList.peek();
					
					if(posting.getDocId() == minDocId) {
						scorer.addPosting(posting);
						postingList.poll();	// point to the next posting
					}
					
					isAllEmpty = false;
				}
			}
			
			// add to the priority queue 
			// if its score is larger than the kth largest score, have been optimized
			if(!isAllEmpty) {
				if(queue.size() == n) {
					if(scorer.rankScore() > queue.peek().score) {
						queue.poll();
						queue.add(new ScoreDoc(scorer));
					}
				} else {
					queue.add(new ScoreDoc(scorer));
				}
			}
		}
		return queue;
	}

	/**
	 * get the min docId of all posting head
	 * @param postingLists
	 * @return
	 */
	private int getMinDocId(List<LinkedList<Posting>> postingLists) {
		Posting posting;
		int minDocId = Integer.MAX_VALUE;
		for(LinkedList<Posting> postingList : postingLists) {
			if(!postingList.isEmpty()) {
				posting = postingList.peek();
				if(posting.getDocId() < minDocId) {
					minDocId = posting.getDocId();
				}
			}
		}
		return minDocId;
	}
	
	/**
	 * todo
	 * @param docId
	 * @return
	 */
	public Document doc(int docId) {
		Document doc = null;
		for (InvertedIndex index : indexList) {
			doc = index.getDoc(docId);
			if(doc != null)
				return doc; 
		}
		return null;
	}
}

